React16-Fiber 源码阅读笔记

前言

提起React,可能脑海里第一印象就是Virtual Dom, 性能好, 渲染快, 之前也看过很多现成的原理分析文章, 但是别人的东西始终是建立在别人的理解之上,
既然现在平时的工作已经大规模使用React及其生态,那么React 源码分析一方面可以加深对React理解,另外一方面学习其内核原理,更好的在工作业务中寻找一个最佳实践。
本次源码阅读的版本是 从2017年11.28的发布的 React 16.2版本

React16 的新特性

下面先了解一下React16的新特性

https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html#changes-in-detail

支持在html元素中传入 自定义属性

1
<div tabIndex="-1" />

允许在render函数中返回节点数组,字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//  React 16.0 允许在render函数中返回节点数组
render() {
return [
"Some text.", // Strings must be wrapped in quotes.
<h2 key="heading-1">A heading</h2>, // Don't forget the keys :)
"More text.",
<h2 key="heading-2">Another heading</h2>,
"Even more text."
];
}
// 支持字符串
render() {
return 'Look ma, no spans!';
}

支持了 Fragment 标签

2017年11月28日最新发布的 React v16.2.0, 主要的更新点如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 通常我们一个节点列表需要一个 div 或者spn 标签来包裹
render() {
return (
// Extraneous div element :(
<div>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</div>
);
}

// 使用 Fragment 特性
render() {
return (
<Fragment>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</Fragment>
);
}

// Fragment也能够被省略, 直接写个<>代替
render() {
return (
<>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</>
);
}
// 还支持 传入 key值, 后期可能会 支持更多的attributes传入, 类似 event 事件绑定
render() {
return (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
);
}

Portals

1
2
3
4
5
6
7
render() {
// 第一个参数是一个react的组件, 第二个参数是一个dom节点
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}

主要的作用就是把组件插入到现有的dom节点中,这个特性对于jq的页面迁移到 react简直是福音.
比如当前有一个页面某个模块是jquery写的, 现在需要把其中某个模块迁移到react,
使用这个特性就非常方便, 就不用特地使用 ReactDom.render()方法单独挂载组件到 dom节点上

采用新的底层架构 Fiber

可以参考一下demo对比两个架构之间的差距:

  1. 老架构 stack https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html
  2. 新架构 fiber https://claudiopro.github.io/react-fiber-vs-stack-demo/fiber.html

更多细节可以参考官方的博客 https://reactjs.org

准备阶段

React的代码好几万行, 盲目的阅读有点大海捞针的感觉, 这里先思考几个问题, 我们总源码中找答案.

  1. Fiber引擎是什么, 它的原理是怎么样的
  2. functionComponent的实现
  3. 新特性中的 render 中return array是怎么实现的
  4. Fragment 实现
  5. virtual dom在 react中具体是以一个什么样的方式存在

现在要做的是:

注意:

  • 由于后续react升级, 本文所引用的部分代码可能会过期
  • 为了优化阅读体验, 会删除部分非核心代码:)

导航

Fiber架构的调用关系链, 方便我们快速浏览整个react-fiber引擎的执行流程
帮助在阅读的过程中,可以快速定位到当前阅读模块在框架中的位置

看到下面一大堆函数 还有一张那么大的图,感觉无从下手?
那就直接进入主题: 进入主题

源码快速导航

Component:

ReactDOM.render,
Component,
React.createElement

legacyRenderSubtreeIntoContainer

enqueueSetState,
enqueueReplaceState,
enqueueForceUpdate

unbatchedUpdates,
batchedUpdates

updateContainerAtExpirationTime

scheduleRootUpdate,
insertUpdateIntoFiber

performWork:

performWork,
scheduleWorkImpl,
requestWork

findHighestPriorityRoot,
performWorkOnRoot

renderRoot,
completeRoot

insertUpdateIntoFiber,
scheduleWorkImpl

Commit:

commitRoot

prepareForCommit

commitAllHostEffects,
commitAllLifeCycles

commitWork,
commitLifeCycles

LifeCycles:

componentDidMount,
componentDidUpdate,
shouldComponentUpdate
componentWillUpdate
componentWillReceiveProps

workLoop:

workLoop,
performUnitOfWork,
beginWork

updateHostRoot,
updateClassComponent

reconcileChildren,
reconcileChildFibers,
reconcileChildrenArray

updateClassInstance,
constructClassInstance,
updateFunctionalComponent

completeWork,
finishClassComponent

Fiber架构图

DSL

下面是架构图的DSL代码, 可以复制代码到 https://yuml.me/ 生成 UML 图片, 也可以直接打开该页面 https://yuml.me/a3c07b49 编辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
[ReactDOM.render{bg:wheat}] -Recursive Call> [render],

[legacyRenderSubtreeIntoContainer] -> [unbatchedUpdates],

[unbatchedUpdates] -> [updateContainer|updateContainerAtExpirationTime],

[updateContainer|updateContainerAtExpirationTime]-> [scheduleRootUpdate],

[scheduleRootUpdate] -> [insertUpdateIntoFiber],

[batchedUpdates] -> [performWork] ,

[scheduleWorkImpl]-> [requestWork],

[performWork] -> [findHighestPriorityRoot],

[requestWork] -> [performWorkOnRoot],
[findHighestPriorityRoot] -> [performWorkOnRoot],

[performWorkOnRoot] -1> [renderRoot],
[performWorkOnRoot] -2> [completeRoot],

[completeRoot{bg:wheat}] -> [commitRoot],


[commitRoot] -> [prepareForCommit],
[prepareForCommit] -1> [commitAllHostEffects],
[prepareForCommit] -2> [commitAllLifeCycles],



[commitAllHostEffects] -While> [commitWork],
[commitWork] -> [HostConfig.commitUpdate],
[commitAllLifeCycles] -Whild>[commitLifeCycles],

[commitLifeCycles] -instance_null> [componentDidMount{bg:springgreen}],
[commitLifeCycles] -instance\!null> [componentDidUpdate{bg:springgreen}],


[renderRoot{bg:turquoise}]-> [workLoop],
[workLoop] -While> [performUnitOfWork],
[performUnitOfWork] -> [beginWork],

[performUnitOfWork] -> [completeWork{bg:thistle}],

[completeWork{bg:thistle}] -> [prepareUpdate|updateHostComponent],

[prepareUpdate|updateHostComponent]-> [markUpdate|appendAllChildren],

[markUpdate|appendAllChildren] -> [HostConfig.appendInitialChild],

[Component{bg:turquoise}]-> [render],
[render] -> [React.createElement],

[React.createElement] -> [legacyRenderSubtreeIntoContainer],

[Component]-> [Component.prototype.setState],

[Component.prototype.setState] -> [enqueueSetState],
[Component.prototype.setState] -> [enqueueReplaceState],
[Component.prototype.setState] -> [enqueueForceUpdate],
[enqueueSetState]-> [insertUpdateIntoFiber],
[enqueueReplaceState]-> [insertUpdateIntoFiber],
[enqueueForceUpdate]-> [insertUpdateIntoFiber],

[insertUpdateIntoFiber]->[scheduleWorkImpl],

[beginWork{bg:turquoise}] -type_HostRoot=> [updateHostRoot],
[beginWork{bg:turquoise}] -type_ClassComponent=> [updateClassComponent],
[beginWork{bg:turquoise}] -type_other...=> [FunctionalComponent|HostText|CallComponent|HostPortal|Fragment|More],
[updateHostRoot] -> [reconcileChildren],
[reconcileChildren]-> [reconcileChildFibers],
[reconcileChildFibers] -> [reconcileChildrenArray{bg:yellowgreen}],

[updateClassComponent] -> [constructClassInstance|mountClassInstance],
[updateClassComponent] -> [updateClassInstance],

[constructClassInstance|mountClassInstance] -> [finishClassComponent],
[updateClassInstance] -1l> [componentWillReceiveProps{bg:springgreen}],
[componentWillReceiveProps{bg:springgreen}] -> [finishClassComponent],
[updateClassInstance] -2> [shouldComponentUpdate{bg:springgreen}],
[shouldComponentUpdate{bg:springgreen}] -> [componentWillUpdate{bg:springgreen}],

[componentWillUpdate{bg:springgreen}] -> [finishClassComponent],

源码阅读

React

首先来看一下react这个对象里面有啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// packages/react/src/React.js
import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
import {forEach, map, count, toArray, only} from './ReactChildren';
import ReactCurrentOwner from './ReactCurrentOwner';
import {
createElement,
createFactory,
cloneElement,
isValidElement,
} from './ReactElement';

var React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
Component,
PureComponent,
unstable_AsyncComponent: AsyncComponent,

Fragment: REACT_FRAGMENT_TYPE,

createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
},
};

我们如果看babel编译过的react项目JSX代码, 通常我们一个下面是一个react的起点, 可以理解为 这个ReactDOM.render做的是吧react的对象和真实的浏览器dom节点关联
, JSX经过babel编译后,实际是调用createElement()创建实例对象

1
ReactDOM.render(React.createElement(<Component/>, null), document.getElementById('portraits_root'));

React.createElement 对象创建

我们先来分析一下 创建组件的入口 createElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
export function createElement(type, config, children) {
var propName;
// Reserved names are extracted
var props = {};

var key = null;
var ref = null;
var self = null;
var source = null;

if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}

self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}

// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}

// Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}

var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,

// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,

// Record the component responsible for creating this element.
_owner: owner,
};
return element;
};

上面的源码主要做三个工作 ,

  1. 把参数config 的挂到props变量里面
  2. 把 children传入到 props.children属性下
  3. 取出组件类中的静态变量defaultProps,并给未在JSX中设置值的属性设置默认值
  4. 返回一个 ReactElement
  5. 再看看 ReactElement的代码, 其实就是把把参数包装一下再加个 $$typeof标识返回
  6. 截止到这里, 可以看到createElement仅仅是返回一个 element对象

React.Children

React.Children提供了对this.props.children的操作的函数。
react/src/ReactChildren文件export的函数如下

1
2
3
4
5
6
7
export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};

Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};


Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};


Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};



/**
* Base class helpers for the updating state of a component.
*/
function PureComponent(props, context, updater) {
// Duplicated from Component.
this.props = props;
this.context = context;
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
var pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

function AsyncComponent(props, context, updater) {
// Duplicated from Component.
this.props = props;
this.context = context;
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}

var asyncComponentPrototype = (AsyncComponent.prototype = new ComponentDummy());
asyncComponentPrototype.constructor = AsyncComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(asyncComponentPrototype, Component.prototype);
asyncComponentPrototype.unstable_isAsyncReactComponent = true;
asyncComponentPrototype.render = function() {
return this.props.children;
};

export {Component, PureComponent, AsyncComponent};

上面的代码是输出三个类Component, PureComponent, AsyncComponent

  1. setState 实际上就是调用
    this.updater.enqueueSetState(this, partialState, callback, ‘setState’);
  2. forceUpdate实际上是调用
    this.updater.enqueueForceUpdate(this, callback, ‘forceUpdate’);
    这一块后面分析Fiber架构会详细深入了解 this.updater.enqueueSetState 和 this.updater.enqueueForceUpdate
  3. PureComponent 和 asyncComponentPrototype 其实就是在Component的基础上分别设置
    isPureReactComponent 和 unstable_isAsyncReactComponent 为true的熟悉, 这个标准在底层的架构中会判断这写标记做特殊判断, 比如 isPureReactComponent= true之后,组件在shoudComponentUpdate中会浅比较渲染前后的props和state,如果没有变化,组件不会进入接下来的生命周期,可以节省不必要的diff操作。

到此我们已经把React的对象分析的差不多了, 但是是不是有还是觉得不知道React是如何运行的? 下面进入React16新一代的架构Fiber 来进行探索

Fiber

分析前面的 React代码, 发现主要就是构造element对象, 那现在如何让整个react 引擎跑起来?
还记得前面的 ReactDOM.render(React.createElement(, null), document.getElementById(‘portraits_root’));

ReactDOM.render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// react/packages/react-dom/src/client/ReactDOM.js
// 先找到 render方法, 发现其实是调用 legacyRenderSubtreeIntoContainer
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},

// 这个方法的工作就是 是把子组件树挂在到 dom 容器中
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// 如果没有root容器就创建一个
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 初始化挂载组件 不需要 bathced 的去update
DOMRenderer.unbatchedUpdates(() => {
// 如果有父组件, 就去挂载这个父组件, 如果没有父组件, 就直接调用DOMRenderer的render方法 渲染组件
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}

上面的render中 ,多次使用到 DOMRenderer 这个对象 ,
下面的我精简了很多function的细节, 只保留了名称, 从这些名称中可以看出来, 下面这些是 浏览器dom操作与react 核心的引擎的桥梁
同样的,DOM、Canvas、Native、VR、WebGL等等平台都有自己的renderer, 也就是说 fiber引擎可以对接不同平台的渲染接口

DOMRenderer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const DOMRenderer = ReactFiberReconciler({
getRootHostContext
getChildHostContext
getPublicInstance
prepareForCommit
resetAfterCommit
createInstance
appendInitialChild
finalizeInitialChildren
prepareUpdate
shouldSetTextContent
shouldDeprioritizeSubtree
createTextInstance
now: ReactDOMFrameScheduling.now,
mutation
commitUpdate
commitTextUpdate
appendChild
appendChildToContainer
insertBefore
insertInContainerBefore
removeChild
hydration
canHydrateTextInstance
getNextHydratableSibling
getFirstHydratableChild
hydrateInstance
hydrateTextInstance
didNotMatchHydratedContainerTextInstance
didNotMatchHydratedTextInstance
didNotHydrateContainerInstance
didNotHydrateInstance
didNotFindHydratableContainerInstance
didNotFindHydratableContainerTextInstance
didNotFindHydratableInstance
didNotFindHydratableTextInstance
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
cancelDeferredCallback: ReactDOMFrameScheduling.cIC,
useSyncScheduling: !enableAsyncSchedulingByDefaultInReactDOM,
});

从上面看到其实DOMRenderer 对象是由ReactFiberReconciler返回的, 下面就真的进入到fiber的代码了

React-Reconciler

生看代码很苦涩, 还得结合理论描述来看

基础的概念:

  1. React Components, Elements, and Instances
  2. 协调算法(Diff算法)
  3. React基本理论概念
  4. React设计原则

协调算法(reconciliation)

协调: React用以比较两棵树的算法,其决定哪些部分需要更改。
更新(update): 即渲染React app的数据发生的一个改变。通常是setState的结果。最终会导致重新渲染。

调度(scheduling)

调度(scheduling): 决定事务什么时候执行的过程。
事务(work): 必须执行的计算。事务一般是由update引起的(比如setState)。

Fiber的主要目标是使React能够充分利用调度, 具体的需要实现:

  • 暂停事务,在一段时间后再接着执行。
  • 分配不同类型事务的优先级。
  • 复用之前已完成的事务。
  • 当事务不再需要时,终止该事务。

Fiber为了实现这些功能, 需要保存堆栈结构在内存里,然后在适当的时机去执行它。这点对于实现我们要的调度功能非常重要。
重要特性是增量渲染:将渲染事务分块并分布到多个帧去完成的能力,
关键特性包括有新的更新时,暂停、终止或重用事务的功能,为不同类型的更新设置优先级的功能.

以上几点概念理论引用自: React Fiber架构

下面可以详细看看 Fiber 引擎的代码,主要是指 React-Reconciler下面的代码, Reconciler(协调器) 就是所谓的 Virtul DOM

React组件更新渲染分为两个阶段:

  1. reconciliation 和 commit, Reconciler主要用于生成需要更新dom操作的列表,这个过程是 一个纯函数的过程, effectList = f(newProps, newState), 这个过程不会受到状态的变化而影响.
  2. 生成 effect list之后, commit effect list才会把变化应用到dom中, 这里需要调用render提供的dom操作接口方法, React-Dom React-Native 等都有提供接口给我Fiber

ReactFiberReconciler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// react/packages/react-reconciler/src/ReactFiberReconciler.js
// ReactFiberReconciler对象

export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
): Reconciler<C, I, TI> {
var {getPublicInstance} = config;

var {
computeAsyncExpiration,
computeUniqueAsyncExpiration,
computeExpirationForFiber,
scheduleWork,
requestWork,
flushRoot,
batchedUpdates,
unbatchedUpdates,
flushSync,
deferredUpdates,
} = ReactFiberScheduler(config);

return {
createContainer

updateContainer

updateContainerAtExpirationTime,

flushRoot,

requestWork,

computeUniqueAsyncExpiration,

batchedUpdates,

unbatchedUpdates,

deferredUpdates,

flushSync,

getPublicRootInstance

};
}

ReactFiberReconciler 对外抛出的方法,
可以看到主要的几个方法都是ReactFiberScheduler返回的, 进一步证明了fiber架构在任务的调度上做了很大的改变
这些api主要是给 React-dom等 rendener的使用的

updateContainerAtExpirationTime

这个方法主要就是调用 scheduleRootUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;

const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}

return scheduleRootUpdate(current, element, expirationTime, callback);
}

scheduleRootUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {


callback = callback === undefined ? null : callback;

const update = {
expirationTime,
partialState: {element},
callback,
isReplace: false,
isForced: false,
next: null,
};
insertUpdateIntoFiber(current, update);
scheduleWork(current, expirationTime);

return expirationTime;
}

这个方法有顾名思义是开始调度根节点的update操作
主要分为三个操作:

  1. 生成一个update对象
  2. 把这个update 插入到 Fiber中
  3. 开始调度工作

看到这里开始接触到 Fiber对象了, 下面来了解一下 Fiber对象到底为何物

typeFiber

下面可以看看一个fiber的对象是怎么样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function FiberNode(
tag: TypeOfWork,
pendingProps: mixed,
key: null | string,
internalContextTag: TypeOfInternalContext,
) {
// Instance
this.tag = tag; // fiber的类型
this.key = key; // key是在协调算法中用来决定fiber是否可以重用的字段
this.type = null; // type描述了它对应的组件。对于复合组件,type就是复合函数或组件的class。对于宿主组件(div, span等),type是一个字符串。
this.stateNode = null;

// Fiber
this.return = null; // 指向fiber树中的父Fiber
this.child = null; // 子fiber(child fiber)指的是组件的render方法返回来的值。
this.sibling = null; // 兄弟fiber(sibling fiber)指代的是render方法返回多个子节点的情况(Fiber中的新特性!), 兄弟fiber形成一个单链表
this.index = 0;

this.ref = null;

// 一个fiber的pendingProps会在它开始执行处设置,memoizedProps则会在执行结尾处设置。
// 当到来的pendingProps和上一个memoizedProps相等时,它意味着fiber的上一次输出可以重用,避免不必要的事务。
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null; // 待更新update队列
this.memoizedState = null;

this.internalContextTag = internalContextTag;

// Effects
this.effectTag = NoEffect; // side effect类型
this.nextEffect = null; // 单链表结构,方便遍历fiber树上有副作用的节点
this.firstEffect = null;
this.lastEffect = null;

this.expirationTime = NoWork;

this.alternate = null; // 在fiber更新时克隆出的镜像fiber,对fiber的修改会标记在这个fiber上, 这个就是 源码中经常出现的working-in-progres
}

insertUpdateIntoFiber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export function insertUpdateIntoFiber<State>(
fiber: Fiber,
update: Update<State>,
): void {
// We'll have at least one and at most two distinct update queues.
const alternateFiber = fiber.alternate;
let queue1 = fiber.updateQueue;
if (queue1 === null) {

queue1 = fiber.updateQueue = createUpdateQueue((null: any));
}

let queue2;
if (alternateFiber !== null) {
queue2 = alternateFiber.updateQueue;
if (queue2 === null) {
queue2 = alternateFiber.updateQueue = createUpdateQueue((null: any));
}
} else {
queue2 = null;
}
queue2 = queue2 !== queue1 ? queue2 : null;


// If there's only one queue, add the update to that queue and exit.
if (queue2 === null) {
insertUpdateIntoQueue(queue1, update);
return;
}

// If either queue is empty, we need to add to both queues.
if (queue1.last === null || queue2.last === null) {
insertUpdateIntoQueue(queue1, update);
insertUpdateIntoQueue(queue2, update);
return;
}

// If both lists are not empty, the last update is the same for both lists
// because of structural sharing. So, we should only append to one of
// the lists.
insertUpdateIntoQueue(queue1, update);
// But we still need to update the `last` pointer of queue2.
queue2.last = update;
}

fiber.updateQueue是一个单向链表,有first和last指针指向链表的头部和尾部。

一个React element会有一个current fiber和一个alternate fiber。alternate fiber又叫working in progress fiber。
这两个fiber都有一个Update Queue。
这两个Queue里面的item的引用是相同的. 区别在于,working in progress fiber会在更新完一个队列项之后将其从队列中移除。
所以working in progress update queue永远是current queue的一个子集。
在更新完成之后,working in progress fiber取代current fiber成为新的current fiber。如果更新中断(有更高优先级的更新插入),
current fiber的update queue就可以作为备份,使得之前中断的更新可以重新开始。

总之, insertUpdateIntoFiber,这个函数处理了将一个update插入到fiber.updateQueue和fiber.alternateFiber.updateQueue两个队列中操作

scheduleWorkImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function scheduleWorkImpl(
fiber: Fiber,
expirationTime: ExpirationTime,
isErrorRecovery: boolean,
) {
recordScheduleUpdate();

let node = fiber;
while (node !== null) {
// Walk the parent path to the root and update each node's
// expiration time.
// 遍历父节点,一直到root节点, 更新每个节点的 expiration time 截止时间(优先级)
if (
node.expirationTime === NoWork ||
node.expirationTime > expirationTime
) {
node.expirationTime = expirationTime;
}
if (node.alternate !== null) {
if (
node.alternate.expirationTime === NoWork ||
node.alternate.expirationTime > expirationTime
) {
node.alternate.expirationTime = expirationTime;
}
}
if (node.return === null) {
if (node.tag === HostRoot) {
const root: FiberRoot = (node.stateNode: any);

checkRootNeedsClearing(root, fiber, expirationTime);

// 当root收到update时, 调用requestWork, 请求更新
requestWork(root, expirationTime);

checkRootNeedsClearing(root, fiber, expirationTime);
} else {
return;
}
}
node = node.return;
}
}

requestWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {

// Add the root to the schedule.
// Check if this root is already part of the schedule.
if (root.nextScheduledRoot === null) {
// This root is not already scheduled. Add it.
root.remainingExpirationTime = expirationTime;
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
} else {
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
}
} else {
// This root is already scheduled, but its priority may have increased.
const remainingExpirationTime = root.remainingExpirationTime;
if (
remainingExpirationTime === NoWork ||
expirationTime < remainingExpirationTime
) {
// Update the priority.
root.remainingExpirationTime = expirationTime;
}
}

if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}

if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, recalculateCurrentTime());
}
return;
}

if (expirationTime === Sync) {
performWork(Sync, null);
} else {
scheduleCallbackWithExpiration(expirationTime);
}
}

requestWork主要做3件事:

  1. 把 root 节点添加到已调度的队列中去
  2. 如果已经在调度的队列中, 则设置 expirationTime (这里的截止时间就是任务执行的优先级)
  3. 调用performWorkOnRoot 或者 performWork来执行更新队列

performWorkOnRoot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
currentTime: ExpirationTime,
) {

// 设置 isRendering 状态
isRendering = true;

// Check if this is async work or sync/expired work.
// 检测 当前是同步的还是异步的任务
if (expirationTime <= currentTime) {
// Flush sync work.
//执行同步的任务
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;


finishedWork = renderRoot(root, expirationTime);
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work.
// 执行异步的任务
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;

// 渲染 root节点
finishedWork = renderRoot(root, expirationTime);
if (finishedWork !== null) {
// We've completed the root. Check the deadline one more time
// before committing.
// 结束渲染之后,生成effect list, 检测一下 当前周期是否还有多余的时间, 如果有多余时间, 先不执行 commiting
if (!shouldYield()) {
// Still time left. Commit the root.
completeRoot(root, finishedWork, expirationTime);
} else {
// There's no time left. Mark this root as complete. We'll come
// back and commit it later.
root.finishedWork = finishedWork;
}
}
}
}

isRendering = false;
}

performWorkOnRoot 主要区分 同步渲染和异步渲染, 调用 renderRoot , completeRoot来执行任务
异步渲染会先判断一下 shouldYield()为false才 执行completeRoot, 也就是说异步渲染会先计算effect list, 但是不 commit effect,
然后根据当前周期剩余的时间来决定是否执行commiting操作

renderRoot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function renderRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
): Fiber | null {
isWorking = true;

// We're about to mutate the work-in-progress tree. If the root was pending
// commit, it no longer is: we'll need to complete it again.
root.isReadyForCommit = false;

// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
if (
root !== nextRoot ||
expirationTime !== nextRenderExpirationTime ||
nextUnitOfWork === null
) {
// Reset the stack and start working from the root.
resetContextStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
expirationTime,
);
}

try {
workLoop(expirationTime);
} catch (e) {
didError = true;
error = e;
}

执行的步骤:

  1. 检测是否有上一次未完成的任务, 如果有, 就回复上个堆栈继续执行
  2. 执行 workLoop

workLoop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function workLoop(expirationTime: ExpirationTime) {
if (
nextRenderExpirationTime === NoWork ||
nextRenderExpirationTime > expirationTime
) {
return;
}

if (nextRenderExpirationTime <= mostRecentCurrentTime) {
// Flush all expired work.
// 执行所有过期的工作
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
// 当前帧还有剩余的时间, 执行异步操作直到截止的时间
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}

performUnitOfWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;

let next = beginWork(current, workInProgress, nextRenderExpirationTime);

if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}

ReactCurrentOwner.current = null;

return next;
}
  1. beginWork, 异步情况会返回一个workLoop里面whild循环中的 nextUnitOfWork,
  2. 如果返回null, 就 completeUnitOfWork ()

一般情况一个react中的事务分为 begin 和 complete 两步, 下面先看 beginWork 的部分

beginWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
if (
workInProgress.expirationTime === NoWork ||
workInProgress.expirationTime > renderExpirationTime
) {
return bailoutOnLowPriority(current, workInProgress);
}

switch (workInProgress.tag) {
case IndeterminateComponent:
return mountIndeterminateComponent(
current,
workInProgress,
renderExpirationTime,
);
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostText:
return updateHostText(current, workInProgress);
case CallHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
workInProgress.tag = CallComponent;
// Intentionally fall through since this is now the same.
case CallComponent:
return updateCallComponent(
current,
workInProgress,
renderExpirationTime,
);
case ReturnComponent:
// A return component is just a placeholder, we can just run through the
// next one immediately.
return null;
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case Fragment:
return updateFragment(current, workInProgress);
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}

beginWork函数是一个入口, 根据fiber节点不同的tag,调用对应的update方法。
比如下面可以里先看看 updateClassComponent 这个方法

updateClassComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
const hasContext = pushContextProvider(workInProgress);

let shouldUpdate;
// 如果当前Class实例为null,就构造一个class实例, 否则就更新 class 实例
if (current === null) {
if (!workInProgress.stateNode) {
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, workInProgress.pendingProps);
mountClassInstance(workInProgress, renderExpirationTime);
shouldUpdate = true;
} else {
invariant(false, 'Resuming work not yet implemented.');
// In a resume, we'll already have an instance we can reuse.
// shouldUpdate = resumeMountClassInstance(workInProgress, renderExpirationTime);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
renderExpirationTime,
);
}
return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
);
}

updateClassComponent 主要判断是否初始化了ClassComponent, 如果没有就constructClassInstance一个实例, mountClassInstance然后挂载实例
如果有实例, 有就执行updateClassInstance, 主要返回 shouldUpdate
最后返回 finishClassComponent() 的结果

updateClassInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);

const oldProps = workInProgress.memoizedProps;
const newProps = workInProgress.pendingProps;
const oldContext = instance.context;
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);

// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.

if (
typeof instance.componentWillReceiveProps === 'function' &&
(oldProps !== newProps || oldContext !== newContext)
) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
newContext,
);
}

// Compute the next state using the memoized state and the update queue.
const oldState = workInProgress.memoizedState;
// TODO: Previous state can be null.
let newState;
if (workInProgress.updateQueue !== null) {
newState = processUpdateQueue(
current,
workInProgress,
workInProgress.updateQueue,
instance,
newProps,
renderExpirationTime,
);
} else {
newState = oldState;
}

if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
)
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
return false;
}

const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);

if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
startPhaseTimer(workInProgress, 'componentWillUpdate');
instance.componentWillUpdate(newProps, newState, newContext);
stopPhaseTimer();

}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}

// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
memoizeProps(workInProgress, newProps);
memoizeState(workInProgress, newState);
}

// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;

return shouldUpdate;
}

这个updateClassInstance主要的工作:

  1. 当前 instance 的 props, state, context备份为老的版本, 比如 oldProps = workInProgress.memoizedProps; newProps = workInProgress.pendingProps;
  2. 调用 callComponentWillReceiveProps
  3. 遍历workInProgress.updateQueue, 执行update, 生成newState
  4. checkShouldComponentUpdate, 这个地方会调用 ShouldComponentUpdate这个钩子函数
  5. 如果shouldUpdate 为true, 调用componentWillUpdate钩子函数
  6. 不管shouldUpdate是什么, 都更新当前 instance 的 props, state, context, 这样子这个work的相当于可以被复用了, 就算没有rerender, 至少更新了instance内部的状态
updateFunctionalComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function updateFunctionalComponent(current, workInProgress) {
const fn = workInProgress.type;
const nextProps = workInProgress.pendingProps;

if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else {
if (workInProgress.memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
// TODO: consider bringing fn.shouldComponentUpdate() back.
// It used to be here.
}

var unmaskedContext = getUnmaskedContext(workInProgress);
var context = getMaskedContext(workInProgress, unmaskedContext);

var nextChildren;

nextChildren = fn(nextProps, context);

// 直接调用 reconcileChildren 进行 diff, 生成 effect list
// 与updateClassInstance对比就可以发现少了生命周期的功能
reconcileChildren(current, workInProgress, nextChildren);

memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}

finishClassComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
shouldUpdate: boolean,
hasContext: boolean,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);

if (!shouldUpdate) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, false);
}

return bailoutOnAlreadyFinishedWork(current, workInProgress);
}

const instance = workInProgress.stateNode;

// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;

// 这个地方调用我们业务代码中的 render方法
nextChildren = instance.render();

// 这个是非常关键的一个方法, 要开始diff virtual dom的变更了
reconcileChildren(current, workInProgress, nextChildren);


// Memoize props and state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
memoizeState(workInProgress, instance.state);
memoizeProps(workInProgress, instance.props);

// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, true);
}

return workInProgress.child;
}

reconcileChildrenAtExpirationTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  function reconcileChildrenAtExpirationTime(
current,
workInProgress,
nextChildren,
renderExpirationTime,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.

// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}

这个方法主要判断当前的节点是否已经挂载并且render过了,
没有挂载: 执行mountChildFibers, 不会做side-effect 的优化, 会直接render这个节点
已经挂载: 执行reconcileChildFibers, 会在virtual-dom中 使用算法优化, 生成最小的 side-effects, 用最小的dom变更去优化渲染性能

reconcileChildFibers

下面也能揭晓最开始 说的react新特性 支持 reder中return数组和字符串
总的来说,这个函数根据newChild的类型调用不同的方法。
newChild可能是一个元素,也可能是一个数组(React16新特性)
如果是reconcile单个元素,以reconcileSingleElement为例比较key和type,如果相同,复用fiber,删除多余的元素(currentFirstChild的sibling),
如果不同,调用createFiberFromElement,返回新创建的。
如果是string,reconcileSingleTextNode
如果是array,reconcileChildrenArray
如果是空,deleteRemainingChildren删除老的子元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
  // This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.

// 支持新的 fragments 标签
// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
if (
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null
) {
newChild = newChild.props.children;
}

// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;

if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);

case REACT_CALL_TYPE:
return placeSingleChild(
reconcileSingleCall(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_RETURN_TYPE:
return placeSingleChild(
reconcileSingleReturn(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}

if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}

if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}

if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}

if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}

// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}

return reconcileChildFibers;
}

reconcileChildrenArray

react最有名的virtual-dom 的diff过程可以用下面的这个方法来表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// This algorithm can't optimize by searching from boths ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
// with that model. If it ends up not being worth the tradeoffs, we can
// add it later.

// Even with a two ended optimization, we'd want to optimize for the case
// where there are few changes and brute force the comparison instead of
// going for the Map. It'd like to explore hitting that path first in
// forward-only mode and only go for the Map once we notice that we need
// lots of look ahead. This doesn't handle reversal as well as two ended
// search but that's unusual. Besides, for the two ended optimization to
// work on Iterables, we'd need to copy the whole set.

// In this first iteration, we'll just live with hitting the bad case
// (adding everything to a Map) in for every insert/move.

// If you change this code, also update reconcileChildrenIterator() which
// uses the same algorithm.



let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;

let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;

// 遍历一遍 新的newChildren数组, 通过 updateSlot来对比 新老数组的元素, 如果是相同元素, 就更新 fiber链表
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}

// fiber是一个链表的数据结构, 这一步操作就是把 newFiber 放到链表的正确的位置, 构建 fiber 链表
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}

// 如果遍历完新数组, 把老数组剩下的元素都删除了
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}

// 如果老的数组已经遍历结束了, 但是新的数组没有结束, 遍历一下新数组剩下元素然后插入
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (!newFiber) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}

// Add all children to a key map for quick lookups.
// 如果没有命中上面两中情况: 那就是发生了移动或者删除的操作, 就把所有老数组元素按key放map里
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

// Keep scanning and use the map to restore deleted items as moves.
// 继续遍历新的数组, 把老数组中有用的元素的插入到新的数组中,
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);

// 如果老数组这里面这个元素在新数组中有, 就插入,到新数组中, 并且在existingChildren 把这个元素删除, 因为最后existingChildren 中的元素都会被删除
if (newFiber) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}

// 删除没有命中上面情况的元素, 就是删除老的数组中 非(老数组 交集 新数组)的元素
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
最后返回的是一个fiber链表的第一个元素
return resultingFirstChild;
}

diff完成之后, 一直return, workInProgress.child = resultingFirstChild,
后面回到 performUnitOfWork函数中, 执行 completeUnitOfWork

completeUnitOfWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
while (true) {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;

const next = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);


const returnFiber = workInProgress.return;
const siblingFiber = workInProgress.sibling;

resetExpirationTime(workInProgress, nextRenderExpirationTime);

if (next !== null) {
stopWorkTimer(workInProgress);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
}
// If completing this work spawned new work, do that next. We'll come
// back here again.
return next;
}

if (returnFiber !== null) {

// 把 子树的 effect list 插入到 effect list of the parent.
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}

// 把当前fiber节点的effect list 也插入到 effect list of the parent.
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if
// needed, by doing multiple passes over the effect list. We don't want
// to schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
}

stopWorkTimer(workInProgress);


if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
// We've reached the root.
const root: FiberRoot = workInProgress.stateNode;
root.isReadyForCommit = true;
return null;
}
}
return null;
}

函数主主体是一个循环, 主要的功能就是调用 completeWork, 同时也执行 把 子树的 effect list 插入到 effect list of the parent.

completeWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {

// 获取组件的的 newProps
const newProps = workInProgress.pendingProps;

// 根据fiber的 tag 来做处理
switch (workInProgress.tag) {
case FunctionalComponent:
return null;
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
popContextProvider(workInProgress);
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelContextObject(workInProgress);
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}

if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
popHydrationState(workInProgress);
// This resets the hacky state to fix isMounted before committing.
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag &= ~Placement;
}

// 最后是调用 updateHostContainer 来更新, 传入包含 effect list 的 workInProgress
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
const instance: I = workInProgress.stateNode;
const currentHostContext = getHostContext();

// 调用 host 传入的 prepareUpdate 方法, 生成需要更新的dom属性
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);

// 最终是调用这个方法来更新dom的
updateHostComponent(
current,
workInProgress,
updatePayload,
type,
oldProps,
newProps,
rootContainerInstance,
);

// 如果 ref 有修改就标记一下ref更新
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}

const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
} else {
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);

appendAllChildren(instance, workInProgress);

// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
}

if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
case HostText: {
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
return null;
}
case CallComponent:
return moveCallToHandlerPhase(
current,
workInProgress,
renderExpirationTime,
);
case CallHandlerPhase:
// Reset the tag to now be a first phase call.
workInProgress.tag = CallComponent;
return null;
case ReturnComponent:
// Does nothing.
return null;
case Fragment:
return null;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
return null;
// Error cases
case IndeterminateComponent:
invariant(
false,
'An indeterminate component should have become determinate before ' +
'completing. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// eslint-disable-next-line no-fallthrough
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}

这个方法主要的根据fiber的 tag 来执行不同的 update
我们可以分析其中的常见的 tag 为 HostComponent的情况,可以看到 调用 host 传入的 prepareUpdate 方法, 生成需要更新的dom属性, 然后执行updateHostComponent方法
prepareUpdate 和 updateHostComponent 都是react-dom里面传入的方法, 同样 在react-native, canvas等其他平台也有同样的定义, 相当于是一个规范的接口来兼容不同的硬件层

updateHostComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
updatePayload: null | PL,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
) {
// If there are no effects associated with this node, then none of our children had any updates.
// This guarantees that we can reuse all of them.
const childrenUnchanged = workInProgress.firstEffect === null;
const currentInstance = current.stateNode;
if (childrenUnchanged && updatePayload === null) {
// No changes, just reuse the existing instance.
// Note that this might release a previous clone.
workInProgress.stateNode = currentInstance;
} else {
let recyclableInstance = workInProgress.stateNode;
let newInstance = cloneInstance(
currentInstance,
updatePayload,
type,
oldProps,
newProps,
workInProgress,
childrenUnchanged,
recyclableInstance,
);
if (
finalizeInitialChildren(
newInstance,
type,
newProps,
rootContainerInstance,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = newInstance;
if (childrenUnchanged) {
// If there are no other effects in this tree, we need to flag this node as having one.
// Even though we're not going to use it for anything.
// Otherwise parents won't know that there are new children to propagate upwards.
markUpdate(workInProgress);
} else {

// 下面就是调用 react-dom提供的接口来来添加变更到集合中, 有点类似于git中的 add 暂存操作, 等待commit
// If children might have changed, we have to add them all to the set.
appendAllChildren(newInstance, workInProgress);
}
}
};

截止到这里, reconcile的操作已经结束, react的两个阶段(reconciliation , commiting) 已经完成了第一步
现在要回到 performWorkOnRoot, 开始执行 completeRoot

completeRoot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function completeRoot(
root: FiberRoot,
finishedWork: Fiber,
expirationTime: ExpirationTime,
): void {
// Check if there's a batch that matches this expiration time.
const firstBatch = root.firstBatch;
if (firstBatch !== null && firstBatch._expirationTime <= expirationTime) {
if (completedBatches === null) {
completedBatches = [firstBatch];
} else {
completedBatches.push(firstBatch);
}
if (firstBatch._defer) {
// This root is blocked from committing by a batch. Unschedule it until
// we receive another update.
root.finishedWork = finishedWork;
root.remainingExpirationTime = NoWork;
return;
}
}

// Commit the root.
root.finishedWork = null;
root.remainingExpirationTime = commitRoot(finishedWork);
}

这个代码先判断一下是不是满足批量操作的优先级, 如果是满足, 就继续等下一个update, 最后一起批量地update
不然就调用commitRoot来提交更新

commitRoot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//提交effect list更新到 dom中
function commitRoot(finishedWork: Fiber): ExpirationTime {
// We keep track of this so that captureError can collect any boundaries
// that capture an error during the commit phase. The reason these aren't
// local to this function is because errors that occur during cWU are
// captured elsewhere, to prevent the unmount from being interrupted.
isWorking = true;
isCommitting = true;
startCommitTimer();
const root: FiberRoot = finishedWork.stateNode;
root.isReadyForCommit = false;

// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;

let firstEffect;
if (finishedWork.effectTag > PerformedWork) {
// A fiber's effect list consists only of its children, not itself. So if
// the root has an effect, we need to add it to the end of the list. The
// resulting list is the set that would belong to the root's parent, if
// it had one; that is, all the effects in the tree including the root.
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
firstEffect = finishedWork.firstEffect;
}

// 调用HostConfig.prepareForCommit的方法, 生成待提交的dom属性变更
prepareForCommit();

// Commit all the side-effects within a tree. We'll do this in two passes.
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
startCommitHostEffectsTimer();
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitAllHostEffects, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 遍历effect list, commit 所有的 effect
commitAllHostEffects();
} catch (e) {
didError = true;
error = e;
}
}
}
stopCommitHostEffectsTimer();

resetAfterCommit();

// The work-in-progress tree is now the current tree. This must come after
// the first pass of the commit phase, so that the previous tree is still
// current during componentWillUnmount, but before the second pass, so that
// the finished work is current during componentDidMount/Update.
root.current = finishedWork;

// In the second pass we'll perform all life-cycles and ref callbacks.
// Life-cycles happen as a separate pass so that all placements, updates,
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
startCommitLifeCyclesTimer();
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitAllLifeCycles, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 遍历 effect list, commit 所有的生命周期, 就是执行 componentDidupdate之类的方法
commitAllLifeCycles();
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureError(nextEffect, error);
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}

isCommitting = false;
isWorking = false;
stopCommitLifeCyclesTimer();
stopCommitTimer();
const remainingTime = root.current.expirationTime;

if (remainingTime === NoWork) {
capturedErrors = null;
failedBoundaries = null;
}

return remainingTime;
}

commitRoot 主要分为三步

  1. prepareForCommit() 调用HostConfig.prepareForCommit的方法, 生成待提交的dom属性变更
  2. 遍历effect list执行 commitAllHostEffects(), commit 所有的 effect,
  3. 遍历 effect list 执行 commitAllLifeCycles(), commit 所有的生命周期, 就是执行 componentDidupdate之类的方法

commitAllHostEffects

// 这个地方就是把所有的 effect都提交了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function commitAllHostEffects() {
// 把所有的effect 都遍历一遍, 然后
while (nextEffect !== null) {
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
}
recordEffect();

const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}

if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}

// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
let primaryEffectTag =
effectTag & ~(Callback | Err | ContentReset | Ref | PerformedWork);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;

// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
isUnmounting = true;
commitDeletion(nextEffect);
isUnmounting = false;
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}

commitWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | PL = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
invariant(
finishedWork.stateNode !== null,
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
const textInstance: TI = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}

清空当前fiber的 updateQueue
根据tag选择HostConfig中的 相关的 commit 方法, 比如当 tag = HostComponent , 调用 commitUpdate方法, 提交dom更新

commitAllLifeCycles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function commitAllLifeCycles() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;

if (effectTag & (Update | Callback)) {
recordEffect();
const current = nextEffect.alternate;
commitLifeCycles(current, nextEffect);
}

if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}

if (effectTag & Err) {
recordEffect();
commitErrorHandling(nextEffect);
}

const next = nextEffect.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
// I'm not actually sure this matters because we can't reset firstEffect
// and lastEffect since they're on every node, not just the effectful
// ones. So we have to clean everything as we reuse nodes anyway.
nextEffect.nextEffect = null;
// Ensure that we reset the effectTag here so that we can rely on effect
// tags to reason about the current life-cycle.
nextEffect = next;
}
}

一个循环遍历 effect-list, 调用 commitLifeCycles 来触发相关的钩子函数

commitLifeCycles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
function commitLifeCycles(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
startPhaseTimer(finishedWork, 'componentDidMount');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
stopPhaseTimer();
} else {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
stopPhaseTimer();
}
}
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
commitCallbacks(updateQueue, instance);
}
return;
}
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
const instance =
finishedWork.child !== null ? finishedWork.child.stateNode : null;
commitCallbacks(updateQueue, instance);
}
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;

// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}

return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}

这个方法主要就是根据不同的fiber.tag 来处理组件生命周期
比如当tag=ClassComponent的时候, 把当前fiber中缓存的 props state赋值给组件实例, 并且调用componentDidUpdate钩子函数来完成一次组件更新

回顾问题

让我们来看看有没有解决最开始思考的问题

  1. Fiber引擎是什么, 它的原理是怎么样的: Fiber
  2. functionComponent的实现: updateFunctionalComponent
  3. 新特性中的 render 中return array是怎么实现的: reconcileChildFibers
  4. Fragment 实现 beginWork -> react/packages/react-reconciler/src/ReactFiberBeginWork.js -> updateFragment
  5. virtual dom在 react中具体是以一个什么样的方式存在: reconcileChildrenArray

在阅读的过程中, 也思考了新的问题:

  1. 调度器怎么样找到下一个要执行的事务单元?
  2. 优先级怎么设置的?
  3. 调度器怎么样知道什么时候暂停和继续事务?
  4. 事务是怎么执行和标记为完成的? (事务分为两个步骤, begin complete,complete操作可以延期执行)
  5. 生命周期是怎么样被调用的?

返回到 导航, 再看一遍框架图, 可能会有不一样的感觉吧!

结束

对于React16的Fiber引擎分析到这里的结束了, 手动撒花!!!!

当前文章的代码解读是基于 17年11月28发布的 React 16.2,因为时间和个人经验有限, 难免有理解错误或者不足的地方, 希望多多指正.


鉴于当前文章篇幅过长, 后续还将继续分析React-Dom相关的, 这一块主要就是 renderer的的工作

  • React-Dom/events
  • React-Dom/client